{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=\"red\">注</font>: 使用 tensorboard 可视化需要安装 tensorflow (TensorBoard依赖于tensorflow库，可以任意安装tensorflow的gpu/cpu版本)\n",
    "\n",
    "```shell\n",
    "pip install tensorflow-cpu\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T06:59:06.984071Z",
     "start_time": "2025-01-21T06:59:06.979527Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-23T08:02:40.149787Z",
     "iopub.status.busy": "2025-01-23T08:02:40.149621Z",
     "iopub.status.idle": "2025-01-23T08:02:42.315431Z",
     "shell.execute_reply": "2025-01-23T08:02:42.314924Z",
     "shell.execute_reply.started": "2025-01-23T08:02:40.149767Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备\n",
    "\n",
    "https://www.kaggle.com/competitions/cifar-10/data\n",
    "\n",
    "```shell\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-23T08:02:42.317136Z",
     "iopub.status.busy": "2025-01-23T08:02:42.316594Z",
     "iopub.status.idle": "2025-01-23T08:02:42.439986Z",
     "shell.execute_reply": "2025-01-23T08:02:42.439389Z",
     "shell.execute_reply.started": "2025-01-23T08:02:42.317113Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "test  train\n"
     ]
    }
   ],
   "source": [
    "!ls competitions/cifar-10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T06:59:07.983879Z",
     "start_time": "2025-01-21T06:59:07.006666Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-23T08:02:42.440885Z",
     "iopub.status.busy": "2025-01-23T08:02:42.440682Z",
     "iopub.status.idle": "2025-01-23T08:02:44.704449Z",
     "shell.execute_reply": "2025-01-23T08:02:44.703914Z",
     "shell.execute_reply.started": "2025-01-23T08:02:42.440863Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(PosixPath('competitions/cifar-10/train/1.png'), 'frog'),\n",
      " (PosixPath('competitions/cifar-10/train/2.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/3.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/4.png'), 'deer'),\n",
      " (PosixPath('competitions/cifar-10/train/5.png'), 'automobile')]\n",
      "[(PosixPath('competitions/cifar-10/test/1.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/2.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/3.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/4.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/5.png'), 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "DATA_DIR = Path(\".\")\n",
    "DATA_DIR1 =Path(\"competitions/cifar-10/\")\n",
    "train_lables_file = DATA_DIR / \"trainLabels.csv\"\n",
    "test_csv_file = DATA_DIR / \"sampleSubmission.csv\" #测试集模板csv文件\n",
    "train_folder = DATA_DIR1 / \"train\"\n",
    "test_folder = DATA_DIR1 / \"test\"\n",
    "\n",
    "#所有的类别\n",
    "class_names = [\n",
    "    'airplane',\n",
    "    'automobile',\n",
    "    'bird',\n",
    "    'cat',\n",
    "    'deer',\n",
    "    'dog',\n",
    "    'frog',\n",
    "    'horse',\n",
    "    'ship',\n",
    "    'truck',\n",
    "]\n",
    "\n",
    "def parse_csv_file(filepath, folder):\n",
    "    \"\"\"Parses csv files into (filename(path), label) format\"\"\"\n",
    "    results = []\n",
    "    #读取所有行\n",
    "    with open(filepath, 'r') as f:\n",
    "#         lines = f.readlines()  为什么加[1:]，可以试这个\n",
    "        #第一行不需要，因为第一行是标签\n",
    "        lines = f.readlines()[1:] \n",
    "    for line in lines:#依次去取每一行\n",
    "        image_id, label_str = line.strip('\\n').split(',')\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "        results.append((image_full_path, label_str)) #得到对应图片的路径和分类\n",
    "    return results\n",
    "\n",
    "#解析对应的文件夹\n",
    "train_labels_info = parse_csv_file(train_lables_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "#打印\n",
    "import pprint\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T06:59:08.015550Z",
     "start_time": "2025-01-21T06:59:07.983879Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-23T08:02:44.705240Z",
     "iopub.status.busy": "2025-01-23T08:02:44.705050Z",
     "iopub.status.idle": "2025-01-23T08:02:44.764055Z",
     "shell.execute_reply": "2025-01-23T08:02:44.763560Z",
     "shell.execute_reply.started": "2025-01-23T08:02:44.705220Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            filepath       class\n",
      "0  competitions/cifar-10/train/1.png        frog\n",
      "1  competitions/cifar-10/train/2.png       truck\n",
      "2  competitions/cifar-10/train/3.png       truck\n",
      "3  competitions/cifar-10/train/4.png        deer\n",
      "4  competitions/cifar-10/train/5.png  automobile\n",
      "                                filepath       class\n",
      "0  competitions/cifar-10/train/45001.png       horse\n",
      "1  competitions/cifar-10/train/45002.png  automobile\n",
      "2  competitions/cifar-10/train/45003.png        deer\n",
      "3  competitions/cifar-10/train/45004.png  automobile\n",
      "4  competitions/cifar-10/train/45005.png    airplane\n",
      "                           filepath class\n",
      "0  competitions/cifar-10/test/1.png   cat\n",
      "1  competitions/cifar-10/test/2.png   cat\n",
      "2  competitions/cifar-10/test/3.png   cat\n",
      "3  competitions/cifar-10/test/4.png   cat\n",
      "4  competitions/cifar-10/test/5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# train_df = pd.DataFrame(train_labels_info)\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000])\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:])\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T06:59:09.221951Z",
     "start_time": "2025-01-21T06:59:08.015550Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-23T08:02:44.764850Z",
     "iopub.status.busy": "2025-01-23T08:02:44.764664Z",
     "iopub.status.idle": "2025-01-23T08:02:46.979833Z",
     "shell.execute_reply": "2025-01-23T08:02:46.979284Z",
     "shell.execute_reply.started": "2025-01-23T08:02:44.764829Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torchvision import transforms\n",
    "\n",
    "class Cifar10Dataset(Dataset):\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "    label_to_idx = {label: idx for idx, label in enumerate(class_names)}\n",
    "    idx_to_label = {idx: label for idx, label in enumerate(class_names)}\n",
    "    def __init__(self, mode, transform=None):\n",
    "        self.df = self.df_map.get(mode, None)\n",
    "        if self.df is None:\n",
    "            raise ValueError(\"mode should be one of train, val, test, but got {}\".format(mode))\n",
    "\n",
    "        self.transform = transform\n",
    "        \n",
    "    def __getitem__(self, index):\n",
    "        img_path, label = self.df.iloc[index]\n",
    "        img = Image.open(img_path).convert('RGB')\n",
    "        # # img 转换为 channel first\n",
    "        # img = img.transpose((2, 0, 1))\n",
    "        # transform\n",
    "        img = self.transform(img)\n",
    "        # label 转换为 idx\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "    \n",
    "    def __len__(self):\n",
    "        return self.df.shape[0]\n",
    "    \n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4914, 0.4822, 0.4465], [0.247, 0.243, 0.261]\n",
    "\n",
    "transforms_train = transforms.Compose([\n",
    "        # resize\n",
    "        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "        # random rotation 40\n",
    "        transforms.RandomRotation(40),\n",
    "        # horizaontal flip\n",
    "        transforms.RandomHorizontalFlip(),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean, std)\n",
    "    ])\n",
    "\n",
    "transforms_eval = transforms.Compose([\n",
    "        # resize\n",
    "        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean, std)\n",
    "    ])\n",
    "\n",
    "train_ds = Cifar10Dataset(\"train\", transforms_train)\n",
    "eval_ds = Cifar10Dataset(\"eval\", transforms_eval) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T06:59:09.225457Z",
     "start_time": "2025-01-21T06:59:09.221951Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-23T08:02:46.980820Z",
     "iopub.status.busy": "2025-01-23T08:02:46.980464Z",
     "iopub.status.idle": "2025-01-23T08:02:46.983940Z",
     "shell.execute_reply": "2025-01-23T08:02:46.983449Z",
     "shell.execute_reply.started": "2025-01-23T08:02:46.980797Z"
    }
   },
   "outputs": [],
   "source": [
    "batch_size = 128\n",
    "train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=4)   \n",
    "eval_dl = DataLoader(eval_ds, batch_size=batch_size, shuffle=False, num_workers=4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T06:59:09.235243Z",
     "start_time": "2025-01-21T06:59:09.225966Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-23T08:02:46.986138Z",
     "iopub.status.busy": "2025-01-23T08:02:46.985812Z",
     "iopub.status.idle": "2025-01-23T08:03:15.589812Z",
     "shell.execute_reply": "2025-01-23T08:03:15.589284Z",
     "shell.execute_reply.started": "2025-01-23T08:02:46.986116Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([-0.2198, -0.2271, -0.1976]), tensor([0.9968, 0.9946, 0.9034]))\n"
     ]
    }
   ],
   "source": [
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T07:44:09.956439Z",
     "start_time": "2025-01-21T07:44:09.952969Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-23T08:03:15.590869Z",
     "iopub.status.busy": "2025-01-23T08:03:15.590443Z",
     "iopub.status.idle": "2025-01-23T08:03:15.597146Z",
     "shell.execute_reply": "2025-01-23T08:03:15.596525Z",
     "shell.execute_reply.started": "2025-01-23T08:03:15.590847Z"
    }
   },
   "outputs": [],
   "source": [
    "class Resdiual(nn.Module):\n",
    "    \"\"\"浅层的残差块，无bottleneck（性能限制）\"\"\"\n",
    "    def __init__(self, input_channels, output_channels, use_1x1conv=False, stride=1):\n",
    "        \"\"\"\n",
    "        残差块\n",
    "        params filters: 过滤器数目，决定输出通道\n",
    "        params use_1x1conv: 是否使用 1x1 卷积，此时 stride=2，进行降采样\n",
    "        params strides: 步长，默认为1，当降采样的时候设置为2\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.conv1 = nn.Conv2d(\n",
    "            in_channels=input_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=stride,\n",
    "            padding=1,\n",
    "        )\n",
    "        self.conv2 = nn.Conv2d(\n",
    "            in_channels=output_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=1,\n",
    "            padding=1,\n",
    "        )  \n",
    "        if use_1x1conv:\n",
    "            # skip connection 的 1x1 卷积，用于改变通道数和降采样，使得最终可以做残差连接\n",
    "            self.conv_sc = nn.Conv2d(\n",
    "                in_channels=input_channels,\n",
    "                out_channels=output_channels,\n",
    "                kernel_size=1,\n",
    "                stride=stride,\n",
    "            )\n",
    "        else:\n",
    "            self.conv_sc = None\n",
    "        \n",
    "        self.bn1 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "        self.bn2 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "        \n",
    "    def forward(self, inputs):\n",
    "        \"\"\"forward\"\"\"\n",
    "        flow = F.relu(self.bn1(self.conv1(inputs))) #卷积->BN->ReLU\n",
    "        flow = self.bn2(self.conv2(flow)) #卷积->BN\n",
    "        # print(f'flow shape: {flow.shape}')\n",
    "        if self.conv_sc:#如果有1x1卷积，就用1x1卷积\n",
    "            inputs = self.conv_sc(inputs)\n",
    "        # print(f'inputs shape: {inputs.shape}')\n",
    "        return F.relu(flow + inputs) #残差连接->ReLU，必须保证flow和inputs的shape相同\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T07:44:13.122065Z",
     "start_time": "2025-01-21T07:44:13.118991Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-23T08:03:15.597911Z",
     "iopub.status.busy": "2025-01-23T08:03:15.597738Z",
     "iopub.status.idle": "2025-01-23T08:03:15.602756Z",
     "shell.execute_reply": "2025-01-23T08:03:15.602146Z",
     "shell.execute_reply.started": "2025-01-23T08:03:15.597893Z"
    }
   },
   "outputs": [],
   "source": [
    "class ResdiualBlock(nn.Module):\n",
    "    \"\"\"若干个 Resdiual 模块堆叠在一起，通常在第一个模块给 skip connection 使用 1x1conv with stride=2\"\"\"\n",
    "    def __init__(self, input_channels, output_channels, num, is_first=False):\n",
    "        \"\"\"\n",
    "        params filters: 过滤器数目\n",
    "        params num: 堆叠几个 Resdiual 模块\n",
    "        params is_first: 是不是第一个block。 最上面一层 Resdiual 的 stride=1,is_first=False,图像尺寸减半，False图像尺寸不变\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential() # 用于存放 Resdiual 模块\n",
    "        self.model.append(Resdiual(\n",
    "            input_channels=input_channels, \n",
    "            output_channels=output_channels, \n",
    "            use_1x1conv=not is_first, \n",
    "            stride=1 if is_first else 2\n",
    "            )) # 第一个 Resdiual 模块，负责通道翻倍,图像的尺寸减半\n",
    "        for _ in range(1, num):\n",
    "            # 堆叠 num 个 Resdiual 模块\n",
    "            self.model.append(Resdiual(\n",
    "                input_channels=output_channels, \n",
    "                output_channels=output_channels,\n",
    "                use_1x1conv=False, stride=1\n",
    "                ))\n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T07:44:20.834700Z",
     "start_time": "2025-01-21T07:44:20.831200Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-23T08:03:15.603478Z",
     "iopub.status.busy": "2025-01-23T08:03:15.603312Z",
     "iopub.status.idle": "2025-01-23T08:03:15.609547Z",
     "shell.execute_reply": "2025-01-23T08:03:15.608899Z",
     "shell.execute_reply.started": "2025-01-23T08:03:15.603460Z"
    }
   },
   "outputs": [],
   "source": [
    "class ResNetForCifar10(nn.Module):\n",
    "    def __init__(self, n=3, num_classes=10):\n",
    "        \"\"\"\n",
    "        params units: 预测类别的数目\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            nn.Conv2d(\n",
    "                in_channels=3,\n",
    "                out_channels=16,\n",
    "                kernel_size=3,\n",
    "                stride=1,\n",
    "            ),  # conv1\n",
    "            nn.BatchNorm2d(16, momentum=0.9, eps=1e-5),\n",
    "            nn.ReLU(),\n",
    "            ResdiualBlock(input_channels=16, output_channels=16, num=2*n, is_first=True),  # conv2_x\n",
    "            ResdiualBlock(input_channels=16, output_channels=32, num=2*n),  # conv3_x\n",
    "            ResdiualBlock(input_channels=32, output_channels=64, num=2*n),  # conv4_x\n",
    "            # average pool\n",
    "            nn.AdaptiveAvgPool2d((1, 1)), #无论输入图片大小，输出都是1x1，把width和height压缩为1\n",
    "            nn.Flatten(),\n",
    "            # fully connected\n",
    "            nn.Linear(in_features=64, out_features=num_classes),\n",
    "            )\n",
    "        \n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 kaiming 均匀分布来初始化全连接层、卷积层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.kaiming_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "        \n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)\n",
    "\n",
    "\n",
    "# for key, value in ResNetForCifar10(num_classes=len(class_names)).named_parameters():\n",
    "#     print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T07:42:15.755683Z",
     "start_time": "2025-01-21T07:42:15.746118Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-23T08:03:15.610214Z",
     "iopub.status.busy": "2025-01-23T08:03:15.610042Z",
     "iopub.status.idle": "2025-01-23T08:03:15.633369Z",
     "shell.execute_reply": "2025-01-23T08:03:15.632854Z",
     "shell.execute_reply.started": "2025-01-23T08:03:15.610196Z"
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total trainable parameters: 565386\n"
     ]
    }
   ],
   "source": [
    "#模型总参数量\n",
    "total_params = sum(p.numel() for p in ResNetForCifar10(num_classes=len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total trainable parameters: {total_params}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T07:44:30.716019Z",
     "start_time": "2025-01-21T07:44:30.689319Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-23T08:03:15.634188Z",
     "iopub.status.busy": "2025-01-23T08:03:15.634006Z",
     "iopub.status.idle": "2025-01-23T08:03:15.693042Z",
     "shell.execute_reply": "2025-01-23T08:03:15.692550Z",
     "shell.execute_reply.started": "2025-01-23T08:03:15.634169Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([1, 10])"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#随机一个张量，喂给上面的model\n",
    "x = torch.randn(1, 3, 32, 32)\n",
    "model = ResNetForCifar10(num_classes=len(class_names))\n",
    "model(x).shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-23T08:03:15.693808Z",
     "iopub.status.busy": "2025-01-23T08:03:15.693638Z",
     "iopub.status.idle": "2025-01-23T08:03:15.794301Z",
     "shell.execute_reply": "2025-01-23T08:03:15.793808Z",
     "shell.execute_reply.started": "2025-01-23T08:03:15.693788Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### TensorBoard 可视化\n",
    "\n",
    "\n",
    "训练过程中可以使用如下命令启动tensorboard服务。\n",
    "\n",
    "```shell\n",
    "tensorboard \\\n",
    "    --logdir=runs \\     # log 存放路径\n",
    "    --host 0.0.0.0 \\    # ip\n",
    "    --port 8848         # 端口\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-23T08:03:15.795253Z",
     "iopub.status.busy": "2025-01-23T08:03:15.794913Z",
     "iopub.status.idle": "2025-01-23T08:03:15.987939Z",
     "shell.execute_reply": "2025-01-23T08:03:15.987419Z",
     "shell.execute_reply.started": "2025-01-23T08:03:15.795232Z"
    }
   },
   "outputs": [],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "            \n",
    "        )\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Save Best\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-23T08:03:15.988878Z",
     "iopub.status.busy": "2025-01-23T08:03:15.988542Z",
     "iopub.status.idle": "2025-01-23T08:03:15.994202Z",
     "shell.execute_reply": "2025-01-23T08:03:15.993563Z",
     "shell.execute_reply.started": "2025-01-23T08:03:15.988855Z"
    }
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Early Stop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-23T08:03:15.994926Z",
     "iopub.status.busy": "2025-01-23T08:03:15.994746Z",
     "iopub.status.idle": "2025-01-23T08:03:15.999370Z",
     "shell.execute_reply": "2025-01-23T08:03:15.998751Z",
     "shell.execute_reply.started": "2025-01-23T08:03:15.994906Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-23T08:03:16.000247Z",
     "iopub.status.busy": "2025-01-23T08:03:16.000083Z",
     "iopub.status.idle": "2025-01-23T08:06:20.069542Z",
     "shell.execute_reply": "2025-01-23T08:06:20.068975Z",
     "shell.execute_reply.started": "2025-01-23T08:03:16.000229Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 7040/7040 [03:03<00:00, 38.42it/s, epoch=19]\n"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())    \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "                \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 20\n",
    "\n",
    "model = ResNetForCifar10(num_classes=10)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用 Rmsprop\n",
    "# Optimizers specified in the torch.optim package\n",
    "# >>> # Assuming optimizer uses lr = 0.05 for all groups\n",
    "# >>> # lr = 0.05     if epoch < 30\n",
    "# >>> # lr = 0.005    if 30 <= epoch < 80\n",
    "# >>> # lr = 0.0005   if epoch >= 80\n",
    "# >>> scheduler = MultiStepLR(optimizer, milestones=[30,80], gamma=0.1)\n",
    "\n",
    "class OptimizerWithScheduler:\n",
    "    def __init__(self, parameters, lr, momentum, weight_decay):\n",
    "        self.optimizer = torch.optim.SGD(parameters, lr=lr, momentum=momentum, weight_decay=weight_decay) # 优化器\n",
    "        self.scheduler = torch.optim.lr_scheduler.MultiStepLR(self.optimizer, milestones=[32_000, 48_000], gamma=0.1) # 学习率衰减\n",
    "        \n",
    "    def step(self):\n",
    "        self.optimizer.step()\n",
    "        self.scheduler.step()\n",
    "        \n",
    "    @property\n",
    "    def param_groups(self):\n",
    "        return self.optimizer.param_groups\n",
    "        \n",
    "    def zero_grad(self):\n",
    "        self.optimizer.zero_grad()\n",
    "        \n",
    "optimizer = OptimizerWithScheduler(model.parameters(), lr=0.1, momentum=0.9, weight_decay=1e-4)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "    \n",
    "exp_name = \"resnet34\"\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/{exp_name}\")\n",
    "tensorboard_callback.draw_model(model, [1, 3, IMAGE_SIZE, IMAGE_SIZE])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(f\"checkpoints/{exp_name}\", save_step=len(train_dl), save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_dl, \n",
    "    eval_dl, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=tensorboard_callback,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_dl)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-23T08:06:20.070472Z",
     "iopub.status.busy": "2025-01-23T08:06:20.070283Z",
     "iopub.status.idle": "2025-01-23T08:06:20.326312Z",
     "shell.execute_reply": "2025-01-23T08:06:20.325621Z",
     "shell.execute_reply.started": "2025-01-23T08:06:20.070450Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "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hjRQEELoXX/99Xz99dfMmzfP96k3eC6wPv30U9avX8+GDRu47rrrgjprVeQxExISmDBhAps2beKHH37gT3/6EzfeeCPp6ens3r2badOmsXz5cvbu3cvChQvZvn07nTt3prCwkClTprBkyRL27t3LsmXLWL16td+cIiFiJdz747PPPmPjxo0Vfn+0a9cOp9PJ888/z65du3j77bd5+eWX/faZNm0aq1ev5s477+S3335j69atvPTSS2RnZ5OQkMD999/Pfffdx1tvvcXOnTtZsWIFr732WoWeu6hemqbx/OLtLNl2pMz9Au04ksfj32zlRH74kcpdR/OY9c0WsvOKKxSrftD15jdWM/WD9ZwqcJKdV8ysb7aw62jZH8AHjQzp2k8/+vVm/rVgK3nFrpDH7z1ewBd7TRzNjey5ON0qTyzYytMLt+FWNd5YtpsvNoQvcd9w4GRpvBG831VV49NfDxre56qGyV6V2kDhzTffJCUlhcsvvzzsfn379uWNN96gY8eOHD58mBkzZnDeeeexadMmUlJSgvYvLi6muLj0m5qTkwOA0+nE6XRGHafT6fSNDGnFubjKcY7q4H2u5XnO1Ske447HmKF643Y6nWiahqqqUV0Mef+AeY+tLoMGDaJevXps27aNa6+91hfLU089xS233EL//v1p0KAB9913n+93EJQvfu9rlJCQwDfffMM999zDOeecQ1JSEpdffjlPP/207/4tW7bw5ptvcuzYMRo3bsydd97J5MmTcblcZGdnM378eLKysmjQoAFjx45l+vTpIeOIJlZVVdE0DafTidnsX74Qb+8LUXEXXnih7/1x3XXX+bbPnj2bm266iREjRtCgQQPuv/9+v/dHNDIyMpg9ezb/+te/mDZtGueffz6zZs1i/Pjxvn06dOjAwoULeeCBB+jTpw+JiYn07duXcePGAfDQQw9hsVh4+OGHOXToEI0bN+b222+v2JMX1WrR5iyeLimt2vP4xX736RMgo1GZi+b8hEvV2J2dxys39g75GJe+sIy8Yhd7svPD7lcWfQHq4q2e5M3hVjGbFP63/hBvLNvDtsdCV1tBcLMCh7v0d/Wq3cdZtfu44fo83uYE1/1nNUdyTeR8+Bsf3d6/zJhX7jrOi0t2AtCqfrKvxG/kWf7VW/q4Jr6+2ve9cLnLTma+/K3s+cNVqVKTIe8nRgkJCWH3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ttolEx977DG1f/9+9eabb9Zreconn3xSrV+/Xh07dkzt3r1bPfnkk0rTNLV69eoWm7k6lVfsaam5//KXv6jExER17Ngx9dNPP6n4+HgVFhamMjIyWmzmLVu2KIPBoF544QV16NAh9dFHHymLxaI+/PBDd5uW+POolGulsY4dO6onnniiyrmW+F6LS5O+qfa8sW+6UvolpaRvaqzM0jc1/nvd6oshpZR64403VMeOHZXJZFJDhw5VP//8c5M997p16xRQ5WPatGlKKdeSiX/7299URESEMpvNavTo0SolJcXjMc6fP6+mTp2q/P39VWBgoJo+fbqyWq0ebXbt2qVGjhypzGazio6OVgsWLKhz5uryAur99993tykqKlIPPfSQCgkJURaLRd18883q7NmzHo9z/PhxNWHCBOXr66vCwsLUX/7yF2W326u8PwMGDFAmk0l16dLF4zlq695771UxMTHKZDKptm3bqtGjR7s7nJaauToXdjgtMfeUKVNUVFSUMplMKjo6Wk2ZMsVjT4SWmFkppb755hvVp08fZTabVY8ePdQ777zjcb4l/jwqpdSqVasUUCWLUi33vRaXJn1T7Xhj33Sl9EtKSd/UWJmVkr6psd9rTSmlajeWJIQQQgghhBDer1VfMySEEEIIIYRovaQYEkIIIYQQQrRKUgwJIYQQQgghWiUphoQQQgghhBCtkhRDQgghhBBCiFZJiiEhhBBCCCFEqyTFkBBCCCGEEKJVkmJICCGEEEII0SpJMSREE7nnnnuYPHlyc8cQQggh3KRvEq2dFENCCCGEEEKIVkmKISEa2BdffEHfvn3x9fWlTZs2xMfH89hjj/HBBx/w1VdfoWkamqaRmJgIwMmTJ7njjjsIDg4mNDSUm266iePHj7sfr/yvdvPmzaNt27YEBgby4IMPUlJS0jwvUAghhNeRvkmI6hmaO4AQV5KzZ88ydepUXnrpJW6++WasVisbN27k7rvvJjU1lby8PN5//30AQkNDsdvtjBs3jri4ODZu3IjBYOD5559n/Pjx7N69G5PJBMCaNWvw8fEhMTGR48ePM336dNq0acMLL7zQnC9XCCGEF5C+SYiaSTEkRAM6e/YspaWl3HLLLcTExADQt29fAHx9fbHZbERGRrrbf/jhhzidTt577z00TQPg/fffJzg4mMTERMaOHQuAyWRi8eLFWCwWevfuzbPPPstjjz3Gc889h04nA7xCCCFqJn2TEDWT71QhGlD//v0ZPXo0ffv25fbbb+fdd98lOzu7xva7du3i8OHDBAQE4O/vj7+/P6GhoRQXF3PkyBGPx7VYLO7bcXFx5Ofnc/LkyUZ9PUIIIbyf9E1C1ExGhoRoQHq9noSEBDZt2sTq1at54403eOqpp9i8eXO17fPz8xk8eDAfffRRlXNt27Zt7LhCCCFaAembhKiZFENCNDBN0xgxYgQjRoxgzpw5xMTEsGzZMkwmEw6Hw6PtoEGD+PTTTwkPDycwMLDGx9y1axdFRUX4+voC8PPPP+Pv70+HDh0a9bUIIYS4MkjfJET1ZJqcEA1o8+bNvPjii2zdupXU1FS+/PJLMjMz6dmzJ506dWL37t2kpKRw7tw57HY7d955J2FhYdx0001s3LiRY8eOkZiYyMMPP8ypU6fcj1tSUsJ9993Hvn37WLFiBXPnzmXmzJkyJ1sIIcQlSd8kRM1kZEiIBhQYGMiGDRt47bXXyMvLIyYmhr///e9MmDCB2NhYEhMTiY2NJT8/n3Xr1nH99dezYcMGnnjiCW655RasVivR0dGMHj3a469xo0ePplu3blx77bXYbDamTp3KM88803wvVAghhNeQvkmImmlKKdXcIYQQNbvnnnvIyclh+fLlzR1FCCGEAKRvElcOGccUQgghhBBCtEpSDAkhhBBCCCFaJZkmJ4QQQgghhGiVZGRICCGEEEII0SpJMSSEEEIIIYRolaQYEkIIIYQQQrRKUgwJIYQQQgghWiUphoQQQgghhBCtkhRDQgghhBBCiFZJiiEhhBBCCCFEqyTFkBBCCCGEEKJVkmJICCGEEEII0Sr9f315QvwT3AeVAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        # axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        # axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "    \n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10)  #横坐标是 steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-23T08:06:20.327136Z",
     "iopub.status.busy": "2025-01-23T08:06:20.326937Z",
     "iopub.status.idle": "2025-01-23T08:06:21.031370Z",
     "shell.execute_reply": "2025-01-23T08:06:21.030799Z",
     "shell.execute_reply.started": "2025-01-23T08:06:20.327114Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_396/3500857114.py:4: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
      "  model.load_state_dict(torch.load(f\"checkpoints/{exp_name}/best.ckpt\", map_location=\"cpu\"))\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.6147\n",
      "accuracy: 0.7978\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(f\"checkpoints/{exp_name}/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, eval_dl, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 推理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-23T08:08:32.535963Z",
     "iopub.status.busy": "2025-01-23T08:08:32.535574Z",
     "iopub.status.idle": "2025-01-23T08:10:44.717196Z",
     "shell.execute_reply": "2025-01-23T08:10:44.716728Z",
     "shell.execute_reply.started": "2025-01-23T08:08:32.535938Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 2344/2344 [02:12<00:00, 17.74it/s]\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>filepath</th>\n",
       "      <th>class</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>competitions/cifar-10/test/1.png</td>\n",
       "      <td>deer</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>competitions/cifar-10/test/2.png</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>competitions/cifar-10/test/3.png</td>\n",
       "      <td>automobile</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>competitions/cifar-10/test/4.png</td>\n",
       "      <td>ship</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>competitions/cifar-10/test/5.png</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                           filepath       class\n",
       "0  competitions/cifar-10/test/1.png        deer\n",
       "1  competitions/cifar-10/test/2.png    airplane\n",
       "2  competitions/cifar-10/test/3.png  automobile\n",
       "3  competitions/cifar-10/test/4.png        ship\n",
       "4  competitions/cifar-10/test/5.png    airplane"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# test_df\n",
    "test_ds = Cifar10Dataset(\"test\", transform=transforms_eval)\n",
    "test_dl = DataLoader(test_ds, batch_size=batch_size, shuffle=False, drop_last=False)\n",
    "\n",
    "preds_collect = []\n",
    "model.eval()\n",
    "for data, fake_label in tqdm(test_dl):\n",
    "    data = data.to(device=device)\n",
    "    logits = model(data)\n",
    "    preds = [test_ds.idx_to_label[idx] for idx in logits.argmax(axis=-1).cpu().tolist()]\n",
    "    preds_collect.extend(preds)\n",
    "    \n",
    "test_df[\"class\"] = preds_collect\n",
    "test_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.status.busy": "2025-01-23T08:08:12.442725Z",
     "iopub.status.idle": "2025-01-23T08:08:12.442966Z",
     "shell.execute_reply": "2025-01-23T08:08:12.442856Z",
     "shell.execute_reply.started": "2025-01-23T08:08:12.442845Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 导出 submission.csv\n",
    "test_df.to_csv(\"submission.csv\", index=False)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
